//==========================================
// Matt Pietrek
// Microsoft Systems Journal, April 1997
// FILE: MSJEXHND.CPP
//==========================================
#include <windows.h>
#include <tchar.h>
#include <dbghelp.h>
#include "msjexhnd.h"
#pragma comment(linker, "/defaultlib:dbghelp.lib")

#include <sstream>
#include <Shlwapi.h>
using namespace std;

//============================== Global Variables =============================

//
// Declare the static variables of the MSJExceptionHandler class
//
TCHAR MSJExceptionHandler::m_szLogFileName[MAX_PATH];
LPTOP_LEVEL_EXCEPTION_FILTER MSJExceptionHandler::m_previousFilter;
HANDLE MSJExceptionHandler::m_hReportFile;
HANDLE MSJExceptionHandler::m_hProcess;

MSJExceptionHandler g_MSJExceptionHandler;  // Declare global instance of class

//============================== Class Methods =============================
#pragma warning(push)
#pragma warning(disable: 4996)
//=============
// Constructor 
//=============
MSJExceptionHandler::MSJExceptionHandler( )
{
    // Install the unhandled exception filter function
    m_previousFilter = SetUnhandledExceptionFilter( MSJUnhandledExceptionFilter );

    // Figure out what the report file will be named, and store it away
    GetModuleFileName( 0, m_szLogFileName, MAX_PATH );

    // Look for the '.' before the "EXE" extension.  Replace the extension
    // with "RPT"
    PTSTR pszDot = _tcsrchr( m_szLogFileName, _T('.') );
    if ( pszDot )
    {
        pszDot++;   // Advance past the '.'
        if ( _tcslen(pszDot) >= 3 )
            _tcscpy( pszDot, _T("RPT") );   // "RPT" -> "Report"
    }

	m_hProcess = GetCurrentProcess();
}

//============
// Destructor 
//============
MSJExceptionHandler::~MSJExceptionHandler( )
{
    SetUnhandledExceptionFilter( m_previousFilter );
}

//==============================================================
// Lets user change the name of the report file to be generated 
//==============================================================
void MSJExceptionHandler::SetLogFileName( PTSTR pszLogFileName )
{
    _tcscpy( m_szLogFileName, pszLogFileName );
}

//===========================================================
// Generate exception dump file 
//===========================================================
LONG WINAPI MSJExceptionHandler::DumpExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo) {
	LONG ret = EXCEPTION_CONTINUE_SEARCH;

	SYSTEMTIME st;
	GetLocalTime(&st);

	char szExeName[MAX_PATH];
	GetModuleFileName(NULL, szExeName, MAX_PATH);

	stringstream filename;
	filename<<string(szExeName).substr(0, string(szExeName).rfind('\\'))<<"\\Dump";

	if (!PathFileExists(filename.str().c_str()))
		CreateDirectory(filename.str().c_str(), NULL);

	filename<<"\\crash."<<st.wYear<<st.wMonth<<st.wDay<<st.wHour<<st.wMinute<<st.wSecond<<".dmp";

	HANDLE dumpfile = CreateFile(filename.str().c_str(), GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_ARCHIVE, NULL);
	if (dumpfile != INVALID_HANDLE_VALUE) {
		MINIDUMP_EXCEPTION_INFORMATION exceptioninfo;

		exceptioninfo.ThreadId = GetCurrentThreadId();
		exceptioninfo.ExceptionPointers = pExceptionInfo;
		exceptioninfo.ClientPointers = TRUE;

		BOOL bOK = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), dumpfile, MiniDumpNormal, &exceptioninfo, NULL, NULL);
		ret = EXCEPTION_EXECUTE_HANDLER;

		CloseHandle(dumpfile);
	}

	return ret;
}

//===========================================================
// Entry point where control comes on an unhandled exception 
//===========================================================
LONG WINAPI MSJExceptionHandler::MSJUnhandledExceptionFilter(
                                    PEXCEPTION_POINTERS pExceptionInfo )
{
    m_hReportFile = CreateFile( m_szLogFileName,
                                GENERIC_WRITE,
                                0,
                                0,
                                OPEN_ALWAYS,
                                FILE_FLAG_WRITE_THROUGH,
                                0 );

    if ( m_hReportFile )
    {
        SetFilePointer( m_hReportFile, 0, 0, FILE_END );

        GenerateExceptionReport( pExceptionInfo );

        CloseHandle( m_hReportFile );
        m_hReportFile = 0;
    }

	DumpExceptionFilter(pExceptionInfo);

    if ( m_previousFilter )
        return m_previousFilter( pExceptionInfo );
    else
        return EXCEPTION_CONTINUE_SEARCH;
}

LONG WINAPI MSJExceptionHandler::PlayerRaisedExceptionFilter(
	PEXCEPTION_POINTERS pExceptionInfo )
{
	m_hReportFile = CreateFile( m_szLogFileName,
		GENERIC_WRITE,
		0,
		0,
		OPEN_ALWAYS,
		FILE_FLAG_WRITE_THROUGH,
		0 );

	if ( m_hReportFile )
	{
		SetFilePointer( m_hReportFile, 0, 0, FILE_END );

		GenerateExceptionReport( pExceptionInfo, false );

		CloseHandle( m_hReportFile );
		m_hReportFile = 0;
	}

	DumpExceptionFilter(pExceptionInfo);

	if ( m_previousFilter )
		return m_previousFilter( pExceptionInfo );
	else
		return EXCEPTION_CONTINUE_SEARCH;
}

//===========================================================================
// Open the report file, and write the desired information to it.  Called by 
// MSJUnhandledExceptionFilter                                               
//===========================================================================
void MSJExceptionHandler::GenerateExceptionReport(
    PEXCEPTION_POINTERS pExceptionInfo, bool bIsCrash )
{
    // Start out with a banner
    _tprintf( _T("\n\n//=====================================================\n") );
	_tprintf( _T("// Shindo's Race System Server Crash Report Module\n") );
	_tprintf( _T("//=====================================================\n") );

	if (!bIsCrash)
		_tprintf( _T("/* This isn't a crash, it's the exception a player raised. */\n") );

	SYSTEMTIME st;
	GetLocalTime(&st);

	_tprintf( _T("Exception occured time: %04d-%02d-%02d %02d:%02d:%02d.%02d\n"), st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);

    PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord;

	// First print information about the type of fault
	_tprintf(   _T("Exception code: %08X %s\n"),
		pExceptionRecord->ExceptionCode,
		GetExceptionString(pExceptionRecord->ExceptionCode) );

	// Now print information about where the fault occured
	TCHAR szFaultingModule[MAX_PATH];
	DWORD section, offset;
	GetLogicalAddress(  pExceptionRecord->ExceptionAddress,
		szFaultingModule,
		sizeof( szFaultingModule ),
		section, offset );

	string strFaultingModule = szFaultingModule;
	strFaultingModule = strFaultingModule.substr(strFaultingModule.rfind('\\')+1, strFaultingModule.length() - strFaultingModule.rfind('\\'));

	_tprintf( _T("Fault address:  %08X %02X:%08X %s\n"),
		pExceptionRecord->ExceptionAddress,
		section, offset, strFaultingModule.c_str() );

	PCONTEXT pCtx = pExceptionInfo->ContextRecord;

	// Show the registers
#ifdef _M_IX86  // Intel Only!
	_tprintf( _T("\nRegisters:\n") );

	_tprintf(_T("EAX:%08X\nEBX:%08X\nECX:%08X\nEDX:%08X\nESI:%08X\nEDI:%08X\n"),
		pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx, pCtx->Esi, pCtx->Edi );

	_tprintf( _T("CS:EIP:%04X:%08X\n"), pCtx->SegCs, pCtx->Eip );
	_tprintf( _T("SS:ESP:%04X:%08X  EBP:%08X\n"),
		pCtx->SegSs, pCtx->Esp, pCtx->Ebp );
	_tprintf( _T("DS:%04X  ES:%04X  FS:%04X  GS:%04X\n"),
		pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs );
	_tprintf( _T("Flags:%08X\n"), pCtx->EFlags );

	SymSetOptions( SYMOPT_DEFERRED_LOADS );

	// Initialize DbgHelp
	if ( !SymInitialize( GetCurrentProcess(), 0, TRUE ) )
		return;

	// Walk the stack using x86 specific code
	IntelStackWalk( pCtx );
#endif

	SymCleanup( GetCurrentProcess() );
}

//======================================================================
// Given an exception code, returns a pointer to a static string with a 
// description of the exception                                         
//======================================================================
LPTSTR MSJExceptionHandler::GetExceptionString( DWORD dwCode )
{
    #define EXCEPTION( x ) case EXCEPTION_##x: return _T(#x);

    switch ( dwCode )
    {
        EXCEPTION( ACCESS_VIOLATION )
        EXCEPTION( DATATYPE_MISALIGNMENT )
        EXCEPTION( BREAKPOINT )
        EXCEPTION( SINGLE_STEP )
        EXCEPTION( ARRAY_BOUNDS_EXCEEDED )
        EXCEPTION( FLT_DENORMAL_OPERAND )
        EXCEPTION( FLT_DIVIDE_BY_ZERO )
        EXCEPTION( FLT_INEXACT_RESULT )
        EXCEPTION( FLT_INVALID_OPERATION )
        EXCEPTION( FLT_OVERFLOW )
        EXCEPTION( FLT_STACK_CHECK )
        EXCEPTION( FLT_UNDERFLOW )
        EXCEPTION( INT_DIVIDE_BY_ZERO )
        EXCEPTION( INT_OVERFLOW )
        EXCEPTION( PRIV_INSTRUCTION )
        EXCEPTION( IN_PAGE_ERROR )
        EXCEPTION( ILLEGAL_INSTRUCTION )
        EXCEPTION( NONCONTINUABLE_EXCEPTION )
        EXCEPTION( STACK_OVERFLOW )
        EXCEPTION( INVALID_DISPOSITION )
        EXCEPTION( GUARD_PAGE )
        EXCEPTION( INVALID_HANDLE )
    }

    // If not one of the "known" exceptions, try to get the string
    // from NTDLL.DLL's message table.

    static TCHAR szBuffer[512] = { 0 };

    FormatMessage(  FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE,
                    GetModuleHandle( _T("NTDLL.DLL") ),
                    dwCode, 0, szBuffer, sizeof( szBuffer ), 0 );

    return szBuffer;
}

//==============================================================================
// Given a linear address, locates the module, section, and offset containing  
// that address.                                                               
//                                                                             
// Note: the szModule paramater buffer is an output buffer of length specified 
// by the len parameter (in characters!)                                       
//==============================================================================
BOOL MSJExceptionHandler::GetLogicalAddress(
        PVOID addr, PTSTR szModule, DWORD len, DWORD& section, DWORD& offset )
{
    MEMORY_BASIC_INFORMATION mbi;

    if ( !VirtualQuery( addr, &mbi, sizeof(mbi) ) )
        return FALSE;

    DWORD hMod = (DWORD)mbi.AllocationBase;

    if ( !GetModuleFileName( (HMODULE)hMod, szModule, len ) )
        return FALSE;

    // Point to the DOS header in memory
    PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod;

    // From the DOS header, find the NT (PE) header
    PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + pDosHdr->e_lfanew);

    PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION( pNtHdr );

    DWORD rva = (DWORD)addr - hMod; // RVA is offset from module load address

    // Iterate through the section table, looking for the one that encompasses
    // the linear address.
    for (   unsigned i = 0;
            i < pNtHdr->FileHeader.NumberOfSections;
            i++, pSection++ )
    {
        DWORD sectionStart = pSection->VirtualAddress;
        DWORD sectionEnd = sectionStart
                    + max(pSection->SizeOfRawData, pSection->Misc.VirtualSize);

        // Is the address in this section???
        if ( (rva >= sectionStart) && (rva <= sectionEnd) )
        {
            // Yes, address is in the section.  Calculate section and offset,
            // and store in the "section" & "offset" params, which were
            // passed by reference.
            section = i+1;
            offset = rva - sectionStart;
            return TRUE;
        }
    }

    return FALSE;   // Should never get here!
}

//============================================================
// Walks the stack, and writes the results to the report file 
//============================================================
void MSJExceptionHandler::IntelStackWalk( PCONTEXT pContext )
{
    _tprintf( _T("\nCall stack:\n") );
    _tprintf( _T("Address   Frame     Logic Addr               Source\n") );

	DWORD dwMachineType = 0;

	STACKFRAME64 sf;
	memset( &sf, 0, sizeof(sf) );

#ifdef _M_IX86
	// Initialize the STACKFRAME structure for the first call.  This is only
	// necessary for Intel CPUs, and isn't mentioned in the documentation.
	sf.AddrPC.Offset       = pContext->Eip;
	sf.AddrPC.Mode         = AddrModeFlat;
	sf.AddrStack.Offset    = pContext->Esp;
	sf.AddrStack.Mode      = AddrModeFlat;
	sf.AddrFrame.Offset    = pContext->Ebp;
	sf.AddrFrame.Mode      = AddrModeFlat;

	dwMachineType = IMAGE_FILE_MACHINE_I386;
#endif

	while ( 1 )
	{
		// Get the next stack frame
		if ( !StackWalk64(  dwMachineType,
						  m_hProcess,
						  GetCurrentThread(),
						  &sf,
						  pContext,
						  0,
						  SymFunctionTableAccess64,
						  SymGetModuleBase64,
						  0  ) )
			break;

		if ( 0 == sf.AddrFrame.Offset ) // Basic sanity check to make sure
			break;                      // the frame is OK.  Bail if not.

		_tprintf( _T("%08X  %08X  "), sf.AddrPC.Offset, sf.AddrFrame.Offset );

		// Get the name of the function for this stack frame entry
		BYTE symbolBuffer[ sizeof(SYMBOL_INFO) + 1024 ];
		PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)symbolBuffer;
		pSymbol->SizeOfStruct = sizeof(symbolBuffer);
		pSymbol->MaxNameLen = 1024;

		DWORD64 symDisplacement = 0;    // Displacement of the input address,
										// relative to the start of the symbol

		if ( SymFromAddr(m_hProcess,sf.AddrPC.Offset,&symDisplacement,pSymbol))
		{
			_tprintf( _T("%hs+%I64X"), pSymbol->Name, symDisplacement );
		}
		else    // No symbol found.  Print out the logical address instead.
		{
			TCHAR szModule[MAX_PATH] = _T("");
			DWORD section = 0, offset = 0;

			GetLogicalAddress(  (PVOID)sf.AddrPC.Offset,
				szModule, sizeof(szModule), section, offset );

			string strFaultingModule = szModule;
			strFaultingModule = strFaultingModule.substr(strFaultingModule.rfind('\\')+1, strFaultingModule.length() - strFaultingModule.rfind('\\'));

			_tprintf( _T("%04X:%08X %s"), section, offset, strFaultingModule.c_str() );
		}

		// Get the source line for this stack frame entry
		IMAGEHLP_LINE64 lineInfo;
		lineInfo.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
		DWORD dwLineDisplacement;
		if ( SymGetLineFromAddr64( m_hProcess, sf.AddrPC.Offset,
			&dwLineDisplacement, &lineInfo ) )
		{
			string strFileName = lineInfo.FileName;
			strFileName = strFileName.substr(strFileName.rfind('\\')+1, strFileName.length() - strFileName.rfind('\\'));
			_tprintf(_T("  %s: line %u"),strFileName.c_str(),lineInfo.LineNumber); 
		}

		_tprintf( _T("\r\n") );
	}
}

//============================================================================
// Helper function that writes to the report file, and allows the user to use 
// printf style formating                                                     
//============================================================================
int __cdecl MSJExceptionHandler::_tprintf(const TCHAR * format, ...)
{
    TCHAR szBuff[1024];
    int retValue;
    DWORD cbWritten;
    va_list argptr;
          
    va_start( argptr, format );
    retValue = wvsprintf( szBuff, format, argptr );
    va_end( argptr );

    WriteFile( m_hReportFile, szBuff, retValue * sizeof(TCHAR), &cbWritten, 0 );

    return retValue;
}
#pragma warning(pop)

